package com.campuscarbooking.api.util;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis锁工具类
 * 实现读写锁机制，支持读读共享，读写互斥，写写互斥
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class RedisLockUtil {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ThreadLocal<String> lockIdThreadLocal = new ThreadLocal<>();
    
    // 锁前缀
    private static final String LOCK_PREFIX = "lock:";
    // 读锁前缀
    private static final String READ_LOCK_PREFIX = LOCK_PREFIX + "read:";
    // 写锁前缀
    private static final String WRITE_LOCK_PREFIX = LOCK_PREFIX + "write:";
    // 读锁计数前缀
    private static final String READ_COUNT_PREFIX = LOCK_PREFIX + "count:";
    
    // 默认锁过期时间，单位秒
    private static final long DEFAULT_EXPIRE = 10;
    
    // 获取锁最大等待时间，单位毫秒
    private static final long MAX_WAIT_TIME = 5000;
    
    // 获取锁重试间隔，单位毫秒
    private static final long RETRY_INTERVAL = 100;
    
    /**
     * 尝试获取读锁
     * @param key 锁的key
     * @return 是否获取成功
     */
    public boolean tryReadLock(String key) {
        return tryReadLock(key, DEFAULT_EXPIRE);
    }
    
    /**
     * 尝试获取读锁
     * @param key 锁的key
     * @param expire 锁过期时间，单位秒
     * @return 是否获取成功
     */
    public boolean tryReadLock(String key, long expire) {
        String readLockKey = READ_LOCK_PREFIX + key;
        String writeLockKey = WRITE_LOCK_PREFIX + key;
        String readCountKey = READ_COUNT_PREFIX + key;
        String lockId = UUID.randomUUID().toString();
        
        long startTime = System.currentTimeMillis();
        
        while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
            // 检查是否有写锁
            Boolean hasWriteLock = redisTemplate.hasKey(writeLockKey);
            if (hasWriteLock != null && hasWriteLock) {
                try {
                    TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                }
                continue;
            }
            
            // 获取读锁
            Boolean success = redisTemplate.opsForValue().setIfAbsent(readLockKey + ":" + lockId, lockId, expire, TimeUnit.SECONDS);
            if (success != null && success) {
                // 读锁计数加1
                redisTemplate.opsForValue().increment(readCountKey, 1);
                lockIdThreadLocal.set(lockId);
                return true;
            }
            
            try {
                TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        
        return false;
    }
    
    /**
     * 释放读锁
     * @param key 锁的key
     */
    public void releaseReadLock(String key) {
        String readLockKey = READ_LOCK_PREFIX + key;
        String readCountKey = READ_COUNT_PREFIX + key;
        String lockId = lockIdThreadLocal.get();
        
        if (lockId != null) {
            // 释放读锁
            redisTemplate.delete(readLockKey + ":" + lockId);
            // 读锁计数减1
            Long count = redisTemplate.opsForValue().decrement(readCountKey, 1);
            // 如果读锁计数为0，删除计数key
            if (count != null && count <= 0) {
                redisTemplate.delete(readCountKey);
            }
            lockIdThreadLocal.remove();
        }
    }
    
    /**
     * 尝试获取写锁
     * @param key 锁的key
     * @return 是否获取成功
     */
    public boolean tryWriteLock(String key) {
        return tryWriteLock(key, DEFAULT_EXPIRE);
    }
    
    /**
     * 尝试获取写锁
     * @param key 锁的key
     * @param expire 锁过期时间，单位秒
     * @return 是否获取成功
     */
    public boolean tryWriteLock(String key, long expire) {
        String writeLockKey = WRITE_LOCK_PREFIX + key;
        String readCountKey = READ_COUNT_PREFIX + key;
        String lockId = UUID.randomUUID().toString();
        
        long startTime = System.currentTimeMillis();
        
        while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
            // 检查是否有读锁
            Object readCount = redisTemplate.opsForValue().get(readCountKey);
            if (readCount != null && (Long)readCount > 0) {
                try {
                    TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                }
                continue;
            }
            
            // 获取写锁
            Boolean success = redisTemplate.opsForValue().setIfAbsent(writeLockKey, lockId, expire, TimeUnit.SECONDS);
            if (success != null && success) {
                lockIdThreadLocal.set(lockId);
                return true;
            }
            
            try {
                TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        
        return false;
    }
    
    /**
     * 释放写锁
     * @param key 锁的key
     */
    public void releaseWriteLock(String key) {
        String writeLockKey = WRITE_LOCK_PREFIX + key;
        String lockId = lockIdThreadLocal.get();
        
        if (lockId != null) {
            // 使用Lua脚本原子性释放锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                            "return redis.call('del', KEYS[1]) " +
                            "else " +
                            "return 0 " +
                            "end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
            
            List<String> keys = Collections.singletonList(writeLockKey);
            redisTemplate.execute(redisScript, keys, lockId);
            lockIdThreadLocal.remove();
        }
    }
} 